package com.credithc.customers.api.aop;

import com.credithc.baseserv.core.codes.CoreCodes;
import com.credithc.baseserv.core.exception.BsException;
import com.credithc.baseserv.core.message.MessageFactory;
import com.credithc.eagleeye.agent.factory.EagleEyeAgentFactory;
import com.credithc.eagleeye.agent.util.HostUtil;
import com.credithc.eagleeye.constants.RpcTypeEnum;
import com.credithc.eagleeye.thread.EagleEyeThreadContext;
import com.credithc.eagleeye.thread.TraceInfo;
import com.credithc.eagleeye.util.FastJsonConvert;
import com.credithc.eagleeye.vo.RpcTraceInfoVo;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.time.FastDateFormat;
import org.apache.ibatis.javassist.*;
import org.apache.ibatis.javassist.bytecode.CodeAttribute;
import org.apache.ibatis.javassist.bytecode.LocalVariableAttribute;
import org.apache.ibatis.javassist.bytecode.MethodInfo;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@SuppressWarnings("ALL")
//@Aspect
//@Component
public class MvcAspectHandler {

    @Autowired
    MessageFactory messageFactory;

    private static final Logger logger = LoggerFactory.getLogger(MvcAspectHandler.class);
    private static final FastDateFormat ISO_DATETIME_TIME_ZONE_FORMAT_WITH_MILLIS = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSZZ");

    /**
     * 获取用户信息
     *
     * @param request
     * @return
     */
    private Map<String, String> getUserInfo(HttpServletRequest request) {
        return new HashMap<String, String>();
    }

    /**
     * 初始化鹰眼数据
     *
     * @param request
     * @param handler
     */
    private void initEagleEyeData(HttpServletRequest request) {
        request.setAttribute("beginTime", System.currentTimeMillis() + "");

        EagleEyeThreadContext.init();
        TraceInfo traceInfo = new TraceInfo();
        traceInfo.setUserInfo(getUserInfo(request));

        RpcTraceInfoVo rpcTraceInfoVo = new RpcTraceInfoVo();
        rpcTraceInfoVo.setRequestDateTime(ISO_DATETIME_TIME_ZONE_FORMAT_WITH_MILLIS.format(new Date()));
        rpcTraceInfoVo.setTraceId(traceInfo.getTraceId());
        rpcTraceInfoVo.setRpcId(traceInfo.getRpcId());
        rpcTraceInfoVo.setRpcType(RpcTypeEnum.HTTP.getRpcName());

        rpcTraceInfoVo.setServiceCategory((String) request.getAttribute("Category"));
        rpcTraceInfoVo.setServiceName((String) request.getAttribute("class_name"));
        rpcTraceInfoVo.setMethodName((String) request.getAttribute("method_name"));
        rpcTraceInfoVo.setRequestJson(FastJsonConvert.convertObjectToJSONWithNullValue(request.getParameterMap()));
//        rpcTraceInfoVo.setRequestJson(FastJsonConvert.convertObjectToJSONWithNullValue((String) request.getAttribute("method_args")));
        rpcTraceInfoVo.setUserInfo(traceInfo.getUserInfo());
        rpcTraceInfoVo.setServerHost(HostUtil.getIp() + ":" + request.getServletPath());
        rpcTraceInfoVo.setClientHost(request.getRemoteAddr());
        request.setAttribute("rpcTraceInfoVo", rpcTraceInfoVo);

        traceInfo.addHierarchy();
        EagleEyeThreadContext.putTraceInfo(traceInfo);
        MDC.put("traceId", traceInfo.getTraceId());
        MDC.put("rpcId", traceInfo.getRpcId());
    }

    /**
     * 发送鹰眼数据
     *
     * @param value
     * @param request
     * @param ex
     */
    private void finishEagleEyeData(HttpServletRequest request, String ex) {
        try {
            RpcTraceInfoVo rpcTraceInfoVo = (RpcTraceInfoVo) request.getAttribute("rpcTraceInfoVo");
            if (rpcTraceInfoVo != null) {
                long beginTime = Long.parseLong((String) request.getAttribute("beginTime"));
                rpcTraceInfoVo.setRunTime(System.currentTimeMillis() - beginTime);
                if (ex == null) {
                    rpcTraceInfoVo.setResult("OK");// OK
                } else {
                    rpcTraceInfoVo.setResult("ERROR");// ERROR
                    rpcTraceInfoVo.setResponseJson(ex);
                }
                EagleEyeAgentFactory.getInstance().send2EagleEye(rpcTraceInfoVo);
            }

        } catch (Exception e) {

        } finally {
            request.removeAttribute("rpcTraceInfoVo");
            EagleEyeThreadContext.removeTraceInfo();
            MDC.remove("traceId");
            MDC.remove("rpcId");
            logger.debug("........ThreadContext.clearSessionUserInfo(鹰眼设置清除).....清除成功");
        }
    }

    @Pointcut("execution(public * com.credithc.customers.api.controller.*.*(..))")
    public void controllerPointCut() {
    }


    @Around("controllerPointCut()")
    public Object arround(ProceedingJoinPoint pjp) {
        RpcTraceInfoVo rpcTraceInfoVo = null;
        String message = null;
        Object value = null;
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        try {
            //初始化鹰眼
            String simpleName = pjp.getTarget().getClass().getSimpleName();//类名
            String method_name = pjp.getSignature().getName();//方法名
            Object[] method_args = pjp.getArgs();//参数数组

            request.setAttribute("Category", "Mvc");
            request.setAttribute("class_name", simpleName);
            request.setAttribute("method_name", method_name);
            request.setAttribute("method_args", method_args);
            this.initEagleEyeData(request);
            rpcTraceInfoVo = (RpcTraceInfoVo) request.getAttribute("rpcTraceInfoVo");
            long beginTime = Long.valueOf((String) request.getAttribute("beginTime"));
            rpcTraceInfoVo.setRunTime(System.currentTimeMillis() - beginTime);
            //执行调用
            value = pjp.proceed();//如果不执行这句，会不执行切面的Before方法及controller的业务方法
            rpcTraceInfoVo.setResponseJson(FastJsonConvert.convertObjectToJSON(value));
        } catch (Throwable e) {
//            e.printStackTrace();
            if (e instanceof BsException) {
                String msg = ((BsException) e).getMsg();
                Integer code = ((BsException) e).getCode();
                value = messageFactory.getInstance(code, msg);
                rpcTraceInfoVo.setResponseJson(FastJsonConvert.convertObjectToJSONWithNullValue(value));
            } else {
                message = e.getMessage();
                if (message == null) {
                    message = "系统异常！";
                }
                value = messageFactory.getInstance(CoreCodes.CORE_SYS_ERROR, "系统异常！");
            }
        } finally {
            request.setAttribute("rpcTraceInfoVo", rpcTraceInfoVo);
            finishEagleEyeData(request, message);
        }
        return value;
    }

    /**
     * 使用javassist来获取方法参数名称
     *
     * @param class_name  类名
     * @param method_name 方法名
     * @return
     * @throws Exception
     */
    private String[] getFieldsName(String class_name, String method_name) throws Exception {
        Class<?> clazz = Class.forName(class_name);
        String clazz_name = clazz.getName();
        ClassPool pool = ClassPool.getDefault();
        ClassClassPath classPath = new ClassClassPath(clazz);
        pool.insertClassPath(classPath);

        CtClass ctClass = pool.get(clazz_name);
        CtMethod ctMethod = ctClass.getDeclaredMethod(method_name);
        MethodInfo methodInfo = ctMethod.getMethodInfo();
        CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
        LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
        if (attr == null) {
            return null;
        }
        String[] paramsArgsName = new String[ctMethod.getParameterTypes().length];
        int pos = Modifier.isStatic(ctMethod.getModifiers()) ? 0 : 1;
        for (int i = 0; i < paramsArgsName.length; i++) {
            paramsArgsName[i] = attr.variableName(i + pos);
        }
        return paramsArgsName;
    }

    /**
     * 打印方法参数值  基本类型直接打印，非基本类型需要重写toString方法
     *
     * @param paramsArgsName  方法参数名数组
     * @param paramsArgsValue 方法参数值数组
     */
    private HashMap buildParams(String[] paramsArgsName, Object[] paramsArgsValue) {
        if (ArrayUtils.isEmpty(paramsArgsName) || ArrayUtils.isEmpty(paramsArgsValue)) {
            logger.info("该方法没有参数");
            return null;
        }
        HashMap<String, Object> paramMap = new HashMap();
//        StringBuffer buffer = new StringBuffer();
        for (int i = 0; i < paramsArgsName.length; i++) {
            //参数名
            String name = paramsArgsName[i];
            //参数值
            Object value = paramsArgsValue[i];
            paramMap.put(name, value.toString());
        }
        return paramMap;
    }

    public static Map<String, String> requestUtil(String request) {
        Map<String, String> map = new HashMap<String, String>();
        strRequest(map, request);
        return map;
    }

    public static String strRequest(Map<String, String> map, String s) {
        int length = s.length();
        int index1 = s.indexOf("=");
        String parm1 = s.substring(0, index1);
        int index2 = s.indexOf("&");
        if (index2 == -1) {
            String parm2 = s.substring(index1 + 1);
            map.put(parm1, parm2);
            return null;
        }
        String parm2 = s.substring(index1 + 1, index2);
        map.put(parm1, parm2);
        return strRequest(map, s.substring(index2 + 1));
    }
}
